#pragma once


#ifndef __CANVAS_PTR_H
#define __CANVAS_PTR_H

#include "Objects.h"

namespace Canvas
{
	template<typename T>
	struct ReferenceCounterOperator
	{
		static __forceinline volatile size_t* CreateCounter(T* reference)
		{
			return new size_t(0);
		}

		static __forceinline void DeleteReference(volatile size_t* counter, void* reference)
		{
			delete counter;
			delete (T*)reference;
		}
	};


	template<typename T>
	class Ptr
	{
		template<typename X>
		friend class Ptr;
	protected:
		typedef void(*Destructor)(volatile size_t*, void*);

		volatile size_t*		counter;
		T*					reference;
		void*				originalReference;
		Destructor			originalDestructor;

		void Inc()
		{
			if (counter)
			{
				INCRC(counter);
			}
		}

		void Dec()
		{
			if (counter)
			{
				if (DECRC(counter) == 0)
				{
					originalDestructor(counter, originalReference);
					counter = 0;
					reference = 0;
					originalReference = 0;
					originalDestructor = 0;
				}
			}
		}

		volatile size_t* Counter()const
		{
			return counter;
		}

		Ptr(volatile size_t* _counter, T* _reference, void* _originalReference, Destructor _originalDestructor)
			:counter(_counter)
			, reference(_reference)
			, originalReference(_originalReference)
			, originalDestructor(_originalDestructor)
		{
			Inc();
		}
	public:

		Ptr()
			:counter(0)
			, reference(0)
			, originalReference(0)
			, originalDestructor(0)
		{
		}

		Ptr(T* pointer)
			:counter(0)
			, reference(0)
			, originalReference(0)
			, originalDestructor(0)
		{
			if (pointer)
			{
				counter = ReferenceCounterOperator<T>::CreateCounter(pointer);
				reference = pointer;
				originalReference = pointer;
				originalDestructor = &ReferenceCounterOperator<T>::DeleteReference;
				Inc();
			}
		}

		Ptr(const Ptr<T>& pointer)
			:counter(pointer.counter)
			, reference(pointer.reference)
			, originalReference(pointer.originalReference)
			, originalDestructor(pointer.originalDestructor)
		{
			Inc();
		}

		Ptr(Ptr<T>&& pointer)
			:counter(pointer.counter)
			, reference(pointer.reference)
			, originalReference(pointer.originalReference)
			, originalDestructor(pointer.originalDestructor)
		{
			pointer.counter = 0;
			pointer.reference = 0;
			pointer.originalReference = 0;
			pointer.originalDestructor = 0;
		}

		template<typename C>
		Ptr(const Ptr<C>& pointer)
			:counter(0)
			, reference(0)
			, originalReference(0)
			, originalDestructor(0)
		{
			T* converted = pointer.Obj();
			if (converted)
			{
				counter = pointer.Counter();
				reference = converted;
				originalReference = pointer.originalReference;
				originalDestructor = pointer.originalDestructor;
				Inc();
			}
		}

		~Ptr()
		{
			Dec();
		}

		template<typename C>
		Ptr<C> Cast()const
		{
			C* converted = dynamic_cast<C*>(reference);
			return Ptr<C>((converted ? counter : 0), converted, originalReference, originalDestructor);
		}

		Ptr<T>& operator=(T* pointer)
		{
			Dec();
			if (pointer)
			{
				counter = ReferenceCounterOperator<T>::CreateCounter(pointer);
				reference = pointer;
				originalReference = pointer;
				originalDestructor = &ReferenceCounterOperator<T>::DeleteReference;
				Inc();
			}
			else
			{
				counter = 0;
				reference = 0;
				originalReference = 0;
				originalDestructor = 0;
			}
			return *this;
		}

		Ptr<T>& operator=(const Ptr<T>& pointer)
		{
			if (this != &pointer)
			{
				Dec();
				counter = pointer.counter;
				reference = pointer.reference;
				originalReference = pointer.originalReference;
				originalDestructor = pointer.originalDestructor;
				Inc();
			}
			return *this;
		}

		Ptr<T>& operator=(Ptr<T>&& pointer)
		{
			if (this != &pointer)
			{
				Dec();
				counter = pointer.counter;
				reference = pointer.reference;
				originalReference = pointer.originalReference;
				originalDestructor = pointer.originalDestructor;

				pointer.counter = 0;
				pointer.reference = 0;
				pointer.originalReference = 0;
				pointer.originalDestructor = 0;
			}
			return *this;
		}

		template<typename C>
		Ptr<T>& operator=(const Ptr<C>& pointer)
		{
			T* converted = pointer.Obj();
			Dec();
			if (converted)
			{
				counter = pointer.counter;
				reference = converted;
				originalReference = pointer.originalReference;
				originalDestructor = pointer.originalDestructor;
				Inc();
			}
			else
			{
				counter = 0;
				reference = 0;
				originalReference = 0;
				originalDestructor = 0;
			}
			return *this;
		}

		bool operator==(const T* pointer)const
		{
			return reference == pointer;
		}

		bool operator!=(const T* pointer)const
		{
			return reference != pointer;
		}

		bool operator>(const T* pointer)const
		{
			return reference>pointer;
		}

		bool operator>=(const T* pointer)const
		{
			return reference >= pointer;
		}

		bool operator<(const T* pointer)const
		{
			return reference<pointer;
		}

		bool operator<=(const T* pointer)const
		{
			return reference <= pointer;
		}

		bool operator==(const Ptr<T>& pointer)const
		{
			return reference == pointer.reference;
		}

		bool operator!=(const Ptr<T>& pointer)const
		{
			return reference != pointer.reference;
		}

		bool operator>(const Ptr<T>& pointer)const
		{
			return reference>pointer.reference;
		}

		bool operator>=(const Ptr<T>& pointer)const
		{
			return reference >= pointer.reference;
		}

		bool operator<(const Ptr<T>& pointer)const
		{
			return reference<pointer.reference;
		}

		bool operator<=(const Ptr<T>& pointer)const
		{
			return reference <= pointer.reference;
		}

		operator bool() const
		{
			return reference != 0;
		}

		T* Obj()const
		{
			return reference;
		}

		T* operator->()const
		{
			return reference;
		}
	};

	template<typename T, typename ...TArgs>
	Ptr<T> MakePtr(TArgs ...args)
	{
		return new T(args...);
	}

	template<typename T>
	struct KeyType<Ptr<T>>
	{
		typedef T* Type;

		static T* GetKeyValue(const Ptr<T>& key)
		{
			return key.Obj();
		}
	};

	template<typename T>
	struct POD<Ptr<T>>
	{
		static const bool Result = false;
	};
}

#endif